/*******************************************************************************
 * Copyright (c) 2009 Dmitry Grushin <dgrushin@gmail.com>.
 * 
 * This file is part of GridMe.
 * 
 * GridMe is free software: you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or
 * (at your option) any later version.
 * 
 * GridMe is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 * 
 * You should have received a copy of the GNU General Public License
 * along with GridMe.  If not, see <http://www.gnu.org/licenses/>.
 * 
 * Contributors:
 *     Dmitry Grushin <dgrushin@gmail.com> - initial API and implementation
 ******************************************************************************/
package com.googlecode.gridme.runtime.schedule.workload;

import java.io.File;
import java.io.FileOutputStream;
import java.io.PrintStream;
import java.text.DateFormat;
import java.util.ArrayList;
import java.util.Date;
import java.util.List;
import java.util.TimeZone;
import java.util.zip.ZipEntry;
import java.util.zip.ZipOutputStream;

import com.googlecode.gridme.runtime.exceptions.GRuntimeException;
import com.googlecode.gridme.runtime.exceptions.LoggerException;

/**
 * In-memory representation of workload file. 
 */
public class SWFWorkload
{
  public static final String GEN_NOTE = "Workload generated by GridMe modeling "
      + "environment - http://gridme.googlecode.com";

  private static DateFormat format;

  private ArrayList<TaskInfoSWF> tasks;
  private WorkloadComment comment;
  private TimeZone timeZone;
  private Date startTime;

  public SWFWorkload()
  {
    tasks = new ArrayList<TaskInfoSWF>();
    comment = new WorkloadComment();
    format = DateFormat.getDateTimeInstance();
    timeZone = TimeZone.getDefault();
    startTime = new Date();
  }

  public static SWFWorkload load(File file, WorkloadErrorHandler handler)
      throws GRuntimeException
  {
    SWFWorkload result = new SWFWorkload();
    SWFParser parser = new SWFParser(file, handler,
        result.comment);

    // Process the log
    TaskInfoSWF task = null;
    while(true)
    {
      task = parser.nextTask();
      if(task == null)
      {
        break;
      }
      result.tasks.add(task);
    }
    parser.finish();

    // Parse time zone and start time
    String tz = result.comment.getValue(WorkloadComment.TIMEZONESTRING);
    if(tz != null)
    {
      result.timeZone = TimeZone.getTimeZone(tz);
    }

    String start = result.comment.getValue(WorkloadComment.UNIXSTARTTIME);
    if(start != null)
    {
      try
      {
        result.startTime.setTime(Long.parseLong(start) * 1000);
      }
      catch(NumberFormatException e)
      {
      }
    }

    return result;
  }

  public List<TaskInfoSWF> getTasks()
  {
    return tasks;
  }

  /**
   * Returns start time of the last task
   */
  public long getLength()
  {
    if(!tasks.isEmpty())
    {
      return tasks.get(tasks.size() - 1).getStartTime();
    }
    return 0;
  }

  public void toFile(File wFile) throws GRuntimeException
  {
    setMeta();

    PrintStream out = null;
    try
    {
      ZipOutputStream zippo = new ZipOutputStream(new FileOutputStream(wFile));
      String zipEntryName = wFile.getName();
      String[] name = wFile.getName().split("\\.");
      if(name.length > 0)
      {
        zipEntryName = name[0] + ".swf";
      }
      zippo.putNextEntry(new ZipEntry(zipEntryName));
      out = new PrintStream(zippo, false, "UTF-8");
    }
    catch(Exception e)
    {
      throw new LoggerException("Unable to create workload file "
          + wFile.getAbsolutePath(), e);
    }

    out.print(comment.toString());
    int counter = 1;
    for(TaskInfoSWF task : tasks)
    {
      out.println("" + counter + " " + task);
      counter++;
    }
    out.close();
  }

  /**
   * Sets comment meta data
   */
  private void setMeta()
  {
    comment.setValue(WorkloadComment.MAXJOBS, "" + tasks.size());
    comment.setValue(WorkloadComment.UNIXSTARTTIME, "" + (startTime.getTime() / 1000));
    comment.setValue(WorkloadComment.TIMEZONESTRING, timeZone.getDisplayName());
    comment.setValue(WorkloadComment.STARTTIME, format.format(startTime));
    
    Date end = new Date(startTime.getTime());
    if(!tasks.isEmpty())
    {
      TaskInfoSWF t = tasks.get(tasks.size() - 1);
      end.setTime(end.getTime() + t.getStartTime() + t.getRealExecutionTime());
    }
    comment.setValue(WorkloadComment.ENDTIME, format.format(end));
    
    int maxNodes = 0;
    for(TaskInfoSWF task : tasks)
    {
      if(task.getNodesMin() > maxNodes)
      {
        maxNodes = task.getNodesMin();
      }
    }
    
    comment.setValue(WorkloadComment.MAXNODES, "" + maxNodes);
    comment.setValue(WorkloadComment.MAXPROCS, "" + maxNodes);
  }

  /**
   * Adds all tasks from other workload to this one.
   */
  public void merge(SWFWorkload workload)
  {
    for(TaskInfoSWF task : workload.getTasks())
    {
      addTask(task);
    }
  }

  /**
   * Inserts task to the workload 
   */
  public void addTask(TaskInfoSWF task)
  {
    int index = 0;
    for(TaskInfoSWF t : tasks)
    {
      if(t.getStartTime() >= task.getStartTime())
      {
        tasks.add(index, task);
        return;
      }
      index++;
    }
    tasks.add(task);
  }

  public TimeZone getTimeZone()
  {
    return timeZone;
  }

  public void setTimeZone(TimeZone timeZone)
  {
    this.timeZone = timeZone;
  }

  public Date getStartTime()
  {
    return startTime;
  }

  public void setStartTime(Date startTime)
  {
    this.startTime = startTime;
  }

  public WorkloadComment getComment()
  {
    return comment;
  }
  
  public void align()
  {
    long start1task = 0;
    
    if(!tasks.isEmpty())
    {
      TaskInfoSWF t1 = tasks.get(0);
      start1task = t1.getStartTime();
    }
    
    startTime.setTime(startTime.getTime() + start1task * 1000);
    
    for(TaskInfoSWF task : tasks)
    {
      task.setStartTime(task.getStartTime() - start1task);
    }
  }
}
